#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

using namespace std;


// volatile int flag = 0;
// //int flag = 0;
// void handler(int signo)
// {
//    std::cout << "signo: " << signo <<std::endl;
//    flag = 1;
//    std::cout << "change flag to: " << flag <<std::endl;
// }

// int main()
// {
//    signal(2, handler);//“CTRL+C”发送2号信号
//    std::cout << "getpid: " << getpid() << std::endl;
//    while(!flag);//while循环本质上就是一次次计算，flag的值就在内存中，执行计算的是CPU，所以CPU的寄存器需要把这个值或者指令搬到寄存器里
//    //然后对0做真假判断再决定是否进行循环或结束，也就是每次都要从内存读取flag的值。改成1之后，下一次循环取值为1，那么while循环条件为假，循环结束。
//    //如果编译器优化的话，CPU直接把flag的值保存在寄存器中，每次在其内部判断真假，不再每次从内存读取，因为编译器发现main函数并没有修改flag的值
//    //虽然信号改了内存中flag的值，但是CPU内的寄存器并不知道，优化之后相当于形成了四字节的内存屏障，这就导致了过度优化（因为死循环了）
//    //所以上面的flag要加上volatile关键字，不加就死循环，这个关键字保证了flag变量在内存中的可见性（即避免过度优化，让编译器尽量读取内存的变量的内容）
//    std::cout << "quit normal!" << std::endl;
// }




// void Print(const sigset_t &pending);
// void handler(int signo)
// {
//    std::cout << "get a sig: " << signo << std::endl;
//    sleep(1);
//    while(true)
//    {
//        sigset_t pending;
//        sigpending(&pending);
//        Print(pending);
//        sleep(1);
//    }
// }
// void Print(const sigset_t &pending)
// {
//    for(int signo = 31; signo > 0; signo--)
//    {
//        if(sigismember(&pending, signo))
//        {
//            std::cout << "1";
//        }
//        else
//        {
//            std::cout << "0";
//        }
//    }
//    std::cout << std::endl;
// }
// int main()
// {
// //    std::cout << "pid: " << getpid() << std::endl;//进入handler函数就意味着在处理这个信号，这个正在处理的信号一定是被屏蔽的，此时其余一些信号（包括这种信号）可能都会被屏蔽
// //    //比如第一次发2号信号就在处理，2号信号就被屏蔽了，而且这个handler函数设置不退出进程，那么第2次收到2号信号（在另一个终端发送kill -2 pid）就可以查看pending比特位发现确实是屏蔽了
// //    struct sigaction act, oact;
// //    act.sa_handler = handler;//struct中的一个属性
// //    sigemptyset(&act.sa_mask);//sa_mask就是操作系统要屏蔽的信号集合，先清空一下
// //    sigaddset(&act.sa_mask, 3);//意思是除了2号信号之外，3号信号也要被屏蔽，所以发送“kill -3 pid”会还想pending位变成1
// //
// //    sigaction(2, &act, &oact);//第一个参数为目标信号，第二个参数为要把信号的默认动作修改为act（输入型参数），第三个参数时返回原始的信号对应的默认方法（输出型参数）


//    //同时处理2、3、4、5号信号并且屏蔽，观察分别发送信号的变化以及解除屏蔽时的现象
//    signal(2, handler);
//    signal(3, handler);
//    signal(4, handler);
//    signal(5, handler);
//    sigset_t mask, omask;
//    sigemptyset(&mask);
//    sigemptyset(&omask);
//    sigaddset(&mask, 2);
//    sigaddset(&mask, 3);
//    sigaddset(&mask, 4);
//    sigaddset(&mask, 5);

//    sigprocmask(SIG_SETMASK, &mask, &omask);//屏蔽信号集合里面的信号

//    printf("getpid: %d\n", getpid());
//    int cnt = 20;
//    while(true)
//    {
//        sigset_t pending;
//        sigpending(&pending);
//        Print(pending);
//        cnt--;
//        sleep(1);
//        if(cnt == 0)
//        {
//            sigprocmask(SIG_SETMASK, &omask, nullptr);//cnt等于0时再解除屏蔽
//            std::cout << "cancel 2,3,4,5 block" << std::endl;
//        }
//    }

//    return 0;
// }



void handler(int signo)
{
   cout <<"get a signo: " << signo << endl;
   //waitpid(-1, nullptr, 0);//注意handler函数是属于父进程的，也就是说这条语句可以让父进程收到信号的时候再回收子进程（可以通过查看进程状态发现确实不是僵尸状态）
   pid_t id = 0;
   //waitpid其实还有一个重要的作用，就是检查子进程是否退出，因为只有退了才能回收
   while((id = waitpid(-1, nullptr, WNOHANG)))//第三个参数不能是0（阻塞），因为可能会出现下面的假设情况，有的进程永远不退出，那这里就永远阻塞等待着
   {//所以要改成WHOHANG，不成功时返回值为0
       if(id <= 0) break;//防止没有子进程时还继续回收
       cout << "回收进程：" << id << endl;
   }
}
int main()
{
    signal(SIGCHLD, handler); //需要获取子进程退出的信息就必须等待
    //signal(SIGCHLD, SIG_IGN); //Linux支持手动忽略SIGCHLD, 忽略之后所有的子进程都不需要父进程进行等待了！！退出后自动由系统回收僵尸进程，这样就可以注释掉上面的handler函数
    //假设6个退出，4个不会退，那么while循环回收6个之后会有第7次检查，因为必须检查了才能知道到底还有没有需要回收的进程
    for(int i = 0; i < 10; i++)//十个子进程退出时会有十个sigchld信号，但是sigchld位图只有一个即只能保留一次的sigchld信号，所以不能只是waitpid(-1, nullptr, 0);
    //wait只能等待一个子进程，如果有多个进程在短时间内都发送了sigchld，就只能记录一个信号而遗漏其他信号，所以需要while循环
    {
        pid_t id = fork();
        if(id == 0)
        {
            cout << "child is running" << endl;
            sleep(5);
            exit(10);
        }
    }

    while(true) sleep(1);
    //sleep(10);//注意休眠过程中如何收到信号，会被打断，处理完信号之后不会返回这里继续休眠，而是执行后面的代码，所以还得是定义下面的循环
   // int cnt = 10;
   // while(cnt--)//5秒后子进程退出，发送信号给父进程，此时能看到handler函数的执行，子进程成为僵尸进程，等到父进程也退出了再回收它
   // {
   //     cout << "cnt: " << cnt << endl;
   //     sleep(1);
   // }
   // wait(NULL);

    return 0;
}



